﻿#ifndef NCSPROPMGR_H
#define NCSPROPMGR_H

#include <ncsppt_export.h>
#include <QVariant>
#include <functional>
#include <limits>
#include <QIcon>
#include <QStringList>
#include <QVariantList>

class NcsPropMgr; 
class NcsPropPrivate; 
class NcsPropMgrPrivate; 
class QWidget; 

//--------------------------------------------------------------------------------------------------
class NCSPPT_EXPORT  NcsProp
{
public:
    virtual ~NcsProp();

    const QList <NcsProp *>& subProperties() const;

    NcsProp* subProp(int i) { return subProperties()[i]; }

    NcsPropMgr *propMgr() const;

    QString label() const; // Label that displayed in front of the prop editor. 
    void setLabel(const QString &text);

    int propType() const; // group, string, int, double, etc. 

    QVariant value() const;

    //! Calling intterface of NcsPropMgr::setValue(); 
    template<typename T> void setValue(const T& t_value);
    
    /** @brief Set prop value to invalid value, which makes hasValue() returns false. 
               This is a  calling intterface of NcsPropMgr::uninitializeProperty(); 
      */
    void uninitialize(); 

    /** Get attribute values. attributes are mainly used for GUI.
        
        Some Pre-defined  Attributes
        -----------------------------

        * 'tooltip' :  tool tip string for current property.
        * 'valuetooltip' :  tool tip string used to explain the meaning of value. 
        
     */
    QVariant getAttrib(const QString& attrib_name); 
    void     setAttrib(const QString& attrib_name, QVariant attrib_value); 
    QString  getAttribStr(const QString& attrib_name)  { return getAttrib(attrib_name).toString(); }

    bool hasValue() const; //! prop是否已经分配了non-null 值. 

    /** Get the text form of property's value. calling interface of NcsPropMgr::valueText();

        @note This funcion calls propMgr->valueText() directly, so if you want to 
        define new formatting method for new property value, implement it in its 
        manager's valueText() function.

        @see NcsPropMgr::valueText(); 
     */
    QString valueText() const; // decendent class should re-write valueText(). 

   /** Get icon for property's value.  call manager's valueIcon() interface directly . 
       @see valueIcon(), NcsPropMgr::valueIcon() 
   */
    QIcon valueIcon() const;

    void addSubProperty(NcsProp *pp);

    /** insert property into sub-prop list. if {after} cannot be found , {pp} will be added into the 
        front of sub-item list. */
    void insertSubProperty(NcsProp *pp, NcsProp *after);

    void removeSubProperty(NcsProp *pp);

    enum PropStatus
    {
        PS_MODIFIED = 0,
        PS_DISABLED = 1,
    };

    bool status(PropStatus status_code) const; 
    void setStatus(PropStatus status_code, bool f_set); 
    void clearStatus(); 

    void setModified(bool is_modified) { setStatus(PS_MODIFIED, is_modified);  }
    //void setEnabled (bool is_enabled)  { setStatus(PS_DISABLED, !is_enabled); }

    bool isModified() const  { return status(PS_MODIFIED);  }
    //bool isEnabled()  const  { return !status(PS_DISABLED); }

protected:
    explicit NcsProp(NcsPropMgr *manager);
    explicit NcsProp(NcsPropMgr *manager, int prop_type, const QString& label);

    /*********** member function used for subclassing NcsProp  ***********/
    
    //! Set property value to {val} WITHOUT type cheking, so its only for private usage. 
    virtual bool setVariantValue(QVariant val); 

    //! Set property type, for derived class only.
    void setPropType(int prop_type); 

    /** Restrict input variant {val} in range.  no restriction is performed by default. 
     *  This interface shoule be overrided if derived class has range restrictions. 
     */

    virtual bool outofRange(QVariant var) { return false; } 
    virtual QVariant restrictInRange(QVariant var) { return var; }

    //! update property value from sub-properties. 
    virtual bool updateFromSubProps() { return false; }  // do nothing by default. 

protected:
    friend class NcsPropMgr;
    friend class NcsPropMgrPrivate;
    friend class NcsPropExt;

    NcsPropPrivate* d = nullptr;
}; // NcsProp

typedef NcsProp* PNcsProp;
Q_DECLARE_METATYPE(NcsProp*)

//--------------------------------------------------------------------------------------------------
template <typename NumType>
class NcsPropNumeric : public NcsProp
{
public:
    void setRange(NumType min, NumType max) 
    {
        if (min > max)
        {
            m_has_ragne = false; 
            return; 
        }
        m_min = min;
        m_max = max; 
        m_has_ragne = true; 
    }

    virtual bool outofRange(QVariant number) override
    { 
        if (m_has_ragne)
        {
            NumType val = number.value<NumType>();
            return val < m_min || val > m_max;
        }
        else 
            return false;
    }

    virtual QVariant restrictInRange(QVariant number) override 
    {
        if (m_has_ragne)
        {
            NumType val = number.value<NumType>();
            val = val < m_min ? m_min : val;
            val = val > m_max ? m_max : val;
            return val;
        }
        else
            return NcsProp::restrictInRange(number);
    }

protected:
    friend class NcsPropMgr;
    friend class NcsPropExt; 
    explicit NcsPropNumeric(NcsPropMgr *manager) : NcsProp(manager) {}

    bool m_has_ragne = false; 
    NumType  m_min = std::numeric_limits<NumType>::min();
    NumType  m_max = std::numeric_limits<NumType>::max();
};

typedef NcsPropNumeric<int> NcsIntProp;
typedef NcsPropNumeric<double> NcsDoubleProp;

class NcsColorProp : public NcsProp
{
protected:
    friend class NcsPropMgr;
    friend class NcsPropExt;
    explicit NcsColorProp(NcsPropMgr *manager) ;

};

class NcsFontProp : public NcsProp
{
public:
    virtual bool updateFromSubProps()  override;
    
protected:
    friend class NcsPropMgr;
    friend class NcsPropExt;
    explicit NcsFontProp(NcsPropMgr *manager) ;

};

class NCSPPT_EXPORT NcsEnumProp: public NcsProp
{
public:
	QStringList optionLabels()  const { return m_option_labels; }
	QVariantList optionData()  const { return m_option_data; }

    QString currentLabel() const 
    {
        int index = currentIndex(); 
        if (index != -1)
            return m_option_labels[index];
        else
            return QString(); 
    }

	int currentIndex() const { return getIndexFromValue(this->value()); }

	int getIndexFromValue(QVariant val) const
    {
		if (val.isNull()) return -1;
        for (int i = 0; i<m_option_data.size() ; ++i)
        {
			if (m_option_data[i] == val) 
                return i; 
        }
        return -1;
    }

    /** Set options' label and data. if data list is empty, the labels will be used as data. 
        Option labels are used as combobox's item text. 
    */
    bool setOptions(const QStringList& option_labels, QVariantList option_data = {}) ;

protected:
    friend class NcsPropMgr;
    friend class NcsPropExt;
    explicit NcsEnumProp(NcsPropMgr *manager) ;
    virtual bool setVariantValue(QVariant val) override ;

    QStringList  m_option_labels; 
    QVariantList m_option_data; 
};

typedef NcsEnumProp NcsIntEnumProp;

class NCSPPT_EXPORT NcsStringEnumProp : public NcsEnumProp
{
protected:
    friend class NcsPropMgr;
    friend class NcsPropExt;
    explicit NcsStringEnumProp(NcsPropMgr *manager);
    virtual bool setVariantValue(QVariant val) override ;
};
//--------------------------------------------------------------------------------------------------

/** 
   @brief A property manager object to add, insert, remove, properties from current property set. 
 
   @note 
      
      We need a centered object to communicate with GUI by signal/slot mechanism, and NcsPropMgr,
      a QObject-derived class,  plays such a role. 
      
      NcsProp objects are created or deleted frequently, but NcsPropMgr is persistent. 
 */

class NCSPPT_EXPORT NcsPropMgr : public QObject
{
    Q_OBJECT
public:

    explicit NcsPropMgr(QObject *parent = 0);
    ~NcsPropMgr();

    //! 这里不刻意区分值类型和Prop类型, 而是统一处理. 
    enum PropType
    {
        PT_GROUP = QVariant::UserType+1,
        PT_INT_ENUM, 
        PT_STRING_ENUM, 

        /* [UserType+1 ~ UserType+1023] are preserved for ncsproptree. */
        PT_USER  = QVariant::UserType + 1024, 
    };

    // 预定义的各种数据类型 , 如果要扩展, 在NcsPropMgr派生类中继续增加定义. 
    static int groupTypeId()      { return PT_GROUP; }
    static int boolTypeId()       { return QVariant::Bool; }
    static int intTypeId()        { return QVariant::Int; }
    static int intEnumTypeId()    { return PT_INT_ENUM; }
    static int enumTypeId()       { return intEnumTypeId(); } // alias of intEnumTypeId()
    static int doubleTypeId()     { return QVariant::Double; }
    static int stringTypeId()     { return QVariant::String; }
    static int stringListTypeId() { return QVariant::StringList; }
    static int stringEnumTypeId() { return NcsPropMgr::PT_STRING_ENUM ; }
    static int fontTypeId()       { return QVariant::Font; }
    static int colorTypeId()      { return QVariant::Color; }

    /** NcsPropMgr has a internal root property inside. this function returns it which can be 
        used as the start point for travelling. 
     */
    NcsProp* rootProp() const ;

    void clear() const;

    enum DeleteBehavior
    {
        DELETE_DIRECTLY,  // default value 
        MOVETOCACHE,
    };

    void setDeleteBehavior(DeleteBehavior  behavior);
    DeleteBehavior deleteBehaivior() const; 

    //----------------------------------------------------------

    typedef std::function<QWidget* (NcsProp*)> EditorFn;  //!< functor for creating editor. 
    
    struct DecoratorResource
    {
        QString text;
        QIcon   icon;
    };

    //!< functor for generate displayable text and icons.
    typedef std::function<DecoratorResource (NcsProp*)> DecoratorFn;  

    virtual NcsProp* addProperty(int prop_type, const QString &label= QString(), 
            EditorFn fn_edit_creator = 0, 
            DecoratorFn decorator = 0);

    /** Remove property from current manager. memory of {p} will also be released . 
     *  Return the total number of property being removed. 
     */
    int removeProperty(NcsProp* p);

    //! 为某个类型指定EditorFactory. 这会覆盖NcsPropMgr中默认的Editor 
    void setEditorFactory(int prop_type, EditorFn); 
    
    //! 为某个prop指定EditorFactory. 这个优先级更高. 
    void setEditorFactory(NcsProp* prop, EditorFn); 

    QStringList getFontFamilies(); 

public Q_SLOTS:
    bool setValue(NcsProp *property, bool   val);
    bool setValue(NcsProp *property, int    val);

    /** Set value for property of double type. both setValue(double_prop, 3.5) or 
        setValue(double_prop, "3.5") are ok. 
        
        Pass string as double value is a preferred way, since it can reduce precision loss 
        during string-double type conversion.

        setValue(double_prop, double) interface will NOT used by internal double editor. 
    */
    bool setValue(NcsProp *property, double val);

    /** Set value for string type. properties of double type can also use this interface. */
    virtual bool setValue(NcsProp *property, const QString& str);
	bool setValue(NcsProp *property, const char* cstr) { return setValue(property, QString(cstr)); }

    bool setValue(NcsProp *property, const QStringList& val);
    bool setValue(NcsProp *property, const QColor& color);
    bool setValue(NcsProp *property, const QFont& font);

    bool slotParentAutoUpdate(NcsProp* sub_prop, const QVariant& val);  // {val} is not requied

Q_SIGNALS:

    //! when property is inserted into sub-property list ( done), this signal will emitted.
    void propertyInserted(NcsProp *prop, NcsProp *parent, NcsProp *after);

    //void propertyChanged(NcsProp *property);
    void propertyRemoved(NcsProp *prop, NcsProp *parent);

    //! property will be destroyed immediately.  now {prop}'s member function is still valid. 
    void propertyDestroying(NcsProp *prop);

    void valueChanged(NcsProp *property, const QVariant &val);
    //void attributeChanged(NcsProp *property, const QString &attribute, const QVariant &val);

protected:
    
    /**确定prop的图标和转文字方法. 扩展类型的时候，在这里增加新类型的转换方法*/
    virtual QString valueText(const NcsProp* prop) const;
    virtual QIcon valueIcon(const NcsProp* prop) const;
    
    //! Set property value to {val} WITHOUT type cheking, so its only for private usage. 
    bool setVariantValue(NcsProp* p, QVariant val); 


    virtual void initializeProperty(NcsProp *property);
    virtual void uninitializeProperty(NcsProp *property);
    
    /*********** member function used for subclassing NcsProp  ***********/
    void setTypeEditorFactoryFn(int prop_type, EditorFn editor_factory);
    void setPropFactoryFn(NcsProp* prop, EditorFn editor_factory);
    void addPropertyToMgr(NcsProp* prop);

	void setDefaultTypeEditor();

private:
    friend class NcsProp;
    friend class NcsPropEdit;
    friend class NcsPropTreePrivate; 
    NcsPropMgrPrivate* d = nullptr;
    Q_DISABLE_COPY(NcsPropMgr)
}; // NcsPropMgr

//--------------------------------------------------------------------------------------------------
template<typename T> 
inline void NcsProp::setValue(const T& t_value)
{
    propMgr()->setValue(this, t_value); 
}

inline void NcsProp::uninitialize() { this->propMgr()->uninitializeProperty(this); }

inline QString NcsProp::valueText() const { return propMgr()->valueText(this); }


/* Get icon for property's value.  call manager's valueIcon() interface directly . 
   It's more convinient for  extending NcsPropMgr if valueIcon() is implemented in NcsPropMgr class, 
*/
inline QIcon NcsProp::valueIcon() const { return propMgr()->valueIcon(this); }


//--------------------------------------------------------------------------------------------------
#include <list>

// Breadth-first travel of property. 
class NCSPPT_EXPORT PropTraverse
{
public:
    template<typename Functor>
    PropTraverse(NcsProp* prop, Functor fn) 
    {
        for (auto p : prop->subProperties()) m_props.emplace_back(p);
        while (!m_props.empty())
        {
            NcsProp* p = m_props.front();
            m_props.pop_front();
            fn(p) ;
            for (auto sub_prop : p->subProperties()) m_props.emplace_back(sub_prop);
        }
    }
   
protected:
    std::list<NcsProp*> m_props = {}; 
};

/** Some utils used for extending NcsPropTree. 
 */
class NCSPPT_EXPORT NcsPropExt
{
public:
    template<typename NcsPropClass>
    static inline NcsPropClass* create(NcsPropMgr* mgr)  { return new NcsPropClass(mgr); }

    //! modify property's value directly WITHOUT ANY type check or signal triggered. 
    static void modifyPropValue(NcsProp* p, QVariant val ) ;

};

//--------------------------------------------------------------------------------------------------

#endif // NCSPROPMGR_H

